module Inch
  module Language
    module Ruby
      module Provider
        module YARD
          module Object
            # Utility class to describe (overloaded) method signatures
            class MethodSignature < Struct.new(:method, :yard_tag)
              attr_reader :method, :docstring

              # @param method [Provider::YARD::Object::MethodObject]
              # @param yard_tag [::YARD::Tags::Tag,nil] if nil, the method's
              #   normal signature is used
              def initialize(method, yard_tag = nil)
                @method = method
                @yard_tag = yard_tag
                @docstring =
                  Provider::YARD::Docstring.new(relevant_object.docstring)
              end

              def all_signature_parameter_names
                (relevant_object.parameters || []).map(&:first)
              end

              def has_code_example?
                if docstring.contains_code_example?
                  true
                else
                  !relevant_object.tags(:example).empty?
                end
              end

              def has_doc?
                !docstring.empty? && !implicit_docstring?
              end

              def parameters
                @parameters ||= all_parameter_names.map do |name|
                  signature_name = in_signature(name)
                  tag = parameter_tag(name) || parameter_tag(signature_name)
                  MethodParameterObject.new(method, name, signature_name, tag)
                end
              end

              # Returns the parameter with the given +name+.
              # @param name [String,Symbol]
              # @return [MethodParameterObject]
              def parameter(name)
                parameters.find { |p| p.name == name.to_s }
              end

              # Returns +true+ if the other signature is identical to self
              # @param other [MethodSignature]
              # @return [Boolean]
              def same?(other)
                all_signature_parameter_names ==
                  other.all_signature_parameter_names
              end

              # Returns the actual signature of the method.
              # @return [String]
              def signature
                relevant_object.signature.gsub(/^(def\ )/, '')
              end

              private

              def all_parameter_names
                all_names = all_signature_parameter_names +
                  parameter_tags.map(&:name)
                all_names.map do |name|
                  normalize_parameter_name(name) if name
                end.compact.uniq
              end

              # Returns +true+ if the docstring was generated by YARD
              def implicit_docstring?
                YARD.implicit_docstring?(docstring, method)
              end

              # Returns how the given parameter is noted in the method's
              # signature.
              #
              # @param name [String] parameter name
              # @return [String]
              def in_signature(name)
                possible_names = [name, "*#{name}", "&#{name}", "#{name}:"]
                (all_signature_parameter_names & possible_names).first
              end

              # Removes block, splat symbols, dollar sign,
              # leading and trailing brackets from a given +name+
              # (sometimes used to indicate optional parameters in overload
              # signatures).
              # @param name [String] parameter name
              # @return [String]
              def normalize_parameter_name(name)
                name.gsub(/[\&\*\$\[\]\:]/, '')
              end

              def parameter_tag(param_name)
                parameter_tags.find do |tag|
                  tag.name == param_name
                end
              end

              def parameter_tags
                relevant_object.tags(:param)
              end

              def relevant_object
                @yard_tag || method.object
              end
            end
          end
        end
      end
    end
  end
end
